#pragma once
#include "KhaosVulkanAdapter.h"

namespace Khaos
{
    // --------------------------------------------------------------------------------------------
    class VulkanAPI : public AllocatedObject
    {
    private:
        VkInstance m_inst;

    public:
        VulkanAPI(VkInstance inst = VK_NULL_HANDLE);

#define _get_vulkan_function_proc(fn) PFN_##fn fn = m_inst ? (PFN_##fn)vkGetInstanceProcAddr(m_inst, #fn) : nullptr

        _get_vulkan_function_proc(vkCreateDebugUtilsMessengerEXT);
        _get_vulkan_function_proc(vkDestroyDebugUtilsMessengerEXT);
        _get_vulkan_function_proc(vkGetPhysicalDeviceSurfacePresentModes2EXT);

        _get_vulkan_function_proc(vkCmdBeginDebugUtilsLabelEXT);
        _get_vulkan_function_proc(vkCmdEndDebugUtilsLabelEXT);
        _get_vulkan_function_proc(vkSetDebugUtilsObjectNameEXT);

        _get_vulkan_function_proc(vkCreateRayTracingPipelinesKHR);
        _get_vulkan_function_proc(vkGetRayTracingShaderGroupHandlesKHR);
        _get_vulkan_function_proc(vkCreateAccelerationStructureKHR);
        _get_vulkan_function_proc(vkDestroyAccelerationStructureKHR);
        _get_vulkan_function_proc(vkGetAccelerationStructureDeviceAddressKHR);
        _get_vulkan_function_proc(vkGetAccelerationStructureBuildSizesKHR);
        _get_vulkan_function_proc(vkCmdBuildAccelerationStructuresKHR);
        _get_vulkan_function_proc(vkCmdTraceRaysKHR);

        _get_vulkan_function_proc(vkCmdPushDescriptorSetKHR);

#undef _get_vulkan_function_proc
    };
     
    
    // --------------------------------------------------------------------------------------------
    enum class VulkanGpuHint
    {
        Discrete,
        Integrated
    };

    class VulkanInstance : public AllocatedObject
    {
    public:
        struct Config
        {
            bool enableValidationLayers = false;
            bool enableDebugLabels = false;
        };

        static const uint32_t APIVersion = VK_API_VERSION_1_3; //VK_API_VERSION_1_0 //VK_MAKE_VERSION(1, 3, 0)

    public:
        VulkanInstance();
        ~VulkanInstance();

        void init(const Config& config);
        void shutdown();

        // api
        const VulkanAPI& api() const { return m_api; }

        // handle
        VkInstance getHandle() const { return m_vkInstance; }
        operator VkInstance() const { return m_vkInstance; }

        // info
        const Config& getConfig() const { return m_config; }
        const std::vector<const char*>& getValidationLayers() const { return m_validationLayers; }

        // adapters
        int getAdaptersCount() const { return (int)m_adapters.size(); }
        VulkanAdapter* getAdapter(int index) const { return m_adapters[index]; }
        VulkanAdapter* selectAdapter(VulkanGpuHint hint = VulkanGpuHint::Discrete) const;

    private:
        void _initDebugInfo(VkDebugUtilsMessengerCreateInfoEXT& debugCreateInfo);
        void _setupLayers(VkInstanceCreateInfo& createInfo, VkDebugUtilsMessengerCreateInfoEXT& debugCreateInfo);
        void _setupExts(VkInstanceCreateInfo& createInfo);
        void _setupDebug(const VkDebugUtilsMessengerCreateInfoEXT& debugCreateInfo);

        void _queryAdapters();

    private:
        VkInstance m_vkInstance = VK_NULL_HANDLE;
        VulkanAPI  m_api;
        Config     m_config;

        // adapters
        std::vector<VulkanAdapter*> m_adapters;

        // debug
        std::vector<const char*> m_validationLayers;
        VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
    };

    extern VulkanInstance* g_vulkanInstance;

    #ifdef VkApi_
    #error can not defined VkApi_
    #endif
    #define VkApi_  g_vulkanInstance->api()
}

